#include <cstdio>

#include "OpenGLImage.h"
#include "opencv/cv.h"
#include "opencv/highgui.h"
#include "GL/glu.h"
#include "GL/glut.h"

using namespace std;

OpenGLImage::OpenGLImage(char* filename) {
	IplImage* img = 0;

	button = 0xdeadbeef;

	img = cvLoadImage(filename, CV_LOAD_IMAGE_UNCHANGED);
	if (!img)
		fprintf(stderr, "Error loading image: %s", filename);
	cvFlip(img, NULL, 0);

	width = img->width;
	height = img->height;

	file_data = new GLubyte[width * height * 3];
	memcpy(file_data, img->imageData, width * height * 3);
	cvReleaseImage(&img);

	glGenTextures(1, &textureID);
	texWidth = width;
	texHeight = height;

	glBindTexture(GL_TEXTURE_2D, textureID);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texWidth, texHeight, 0, GL_BGR,
			GL_UNSIGNED_BYTE, file_data);
}

void OpenGLImage::draw() {
	GLint vpStart[4], vpEnd[4];

	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, textureID);
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glBegin(GL_QUADS);
	{
		glTexCoord2d(0, 0);
		glVertex2d(-1, -1);

		glTexCoord2d(((double) width - 1) / texWidth, 0);
		glVertex2d(1, -1);

		glTexCoord2d(((double) width - 1) / texWidth, ((double) height - 1)
				/ texHeight);
		glVertex2d(1, 1);

		glTexCoord2d(0, ((double) height - 1) / texHeight);
		glVertex2d(-1, 1);
	}
	glEnd();
	glDisable(GL_TEXTURE_2D);

	if (start[0].isValid()) {
		convWinPtToView(vpStart, start);
		glViewport(vpStart[0], vpStart[1], vpStart[2], vpStart[3]);
		drawQuad(start, 0., .33, 0.);

	}

	if (end[0].isValid()) {
		convWinPtToView(vpEnd, end);
		glViewport(vpEnd[0], vpEnd[1], vpEnd[2], vpEnd[3]);
		drawQuad(end, 0., .66, 0.);
	}
}

bool OpenGLImage::convWinPtToTexPt(TexPt startTexPts[4], TexPt endTexPts[4]) {
	if (!start[0].isValid() || !end[0].isValid())
		return false;

	for (int i = 0; i < 4; i++) {
		startTexPts[i].x = (GLdouble) (start[i].x & (width - 1)) / texWidth;
		startTexPts[i].y = (GLdouble) (start[i].y & (height - 1)) / texHeight;
		endTexPts[i].x = (GLdouble) (end[i].x & (width - 1)) / texWidth;
		endTexPts[i].y = (GLdouble) (end[i].y & (height - 1)) / texHeight;
	}

	return true;
}

void OpenGLImage::convViewToWinPt(WinPt quadPts[4], GLint viewport[4]) {
	quadPts[0].x = viewport[0];
	quadPts[0].y = viewport[1];
	quadPts[2].x = viewport[0] + viewport[2];
	quadPts[2].y = viewport[1] + viewport[3];
	quadPts[1].x = quadPts[2].x;
	quadPts[1].y = quadPts[0].y;
	quadPts[3].x = quadPts[0].x;
	quadPts[3].y = quadPts[2].y;
}

void OpenGLImage::convWinPtToView(GLint viewport[4], WinPt quadPts[4]) {
	int minX, minY, maxX, maxY;
	minX = quadPts[0].x, minY = quadPts[0].y;
	maxX = quadPts[0].x, maxY = quadPts[0].y;

	for (int i = 1; i < 4; ++i) {
		if (quadPts[i].x < minX)
			minX = quadPts[i].x;
		else if (quadPts[i].x > maxX)
			maxX = quadPts[i].x;

		if (quadPts[i].y < minY)
			minY = quadPts[i].y;
		else if (quadPts[i].y > maxY)
			maxY = quadPts[i].y;
	}

	viewport[0] = minX;
	viewport[1] = minY;
	viewport[2] = maxX - minX;
	viewport[3] = maxY - minY;
}

void OpenGLImage::drawQuad(WinPt quadPts[4], GLfloat red, GLfloat green,
		GLfloat blue) {
	GLdouble ox[4], oy[4], oz, model[16], proj[16];
	GLint view[4];

	glGetDoublev(GL_MODELVIEW_MATRIX, model);
	glGetDoublev(GL_PROJECTION_MATRIX, proj);
	glGetIntegerv(GL_VIEWPORT, view);
	for (int i = 0; i < 4; ++i) {
		gluUnProject(quadPts[i].x, quadPts[i].y, 0., model, proj, view, &ox[i],
				&oy[i], &oz);
	}

	glColor3f(red, green, blue);
	glLineWidth(2.);
	glBegin(GL_LINE_LOOP);
	{
		for (int i = 0; i < 4; i++) {
			glVertex2d(ox[i], oy[i]);
		}
	}
	glEnd();

	glColor3f(1., 0., 0.);
	glPointSize(8.);
	glBegin(GL_POINTS);
	{
		for (int i = 0; i < 4; i++) {
			glVertex2d(ox[i], oy[i]);
		}
	}
	glEnd();
}

void OpenGLImage::initQuad(WinPt quad[4], int x, int y) {
	GLint viewport[4] = { x, y, HIT_BOX, HIT_BOX };
	convViewToWinPt(quad, viewport);
}

void OpenGLImage::finalizeQuad(WinPt quad[4], int x, int y) {
	GLint viewport[4] = { quad[0].x, quad[0].y, x - quad[0].x, y - quad[0].y };
	convViewToWinPt(quad, viewport);
}

void OpenGLImage::mouseFunc(int button, int state, int x, int y) {
	switch (button) {
	case GLUT_LEFT_BUTTON:
		if (state == GLUT_DOWN) {
			this->button = GLUT_LEFT_BUTTON;
			initQuad(start, x, y);
		} else {
			this->button = 0xdeadbeef;
		}
		break;
	case GLUT_RIGHT_BUTTON:
		if (state == GLUT_DOWN) {
			this->button = GLUT_RIGHT_BUTTON;
			initQuad(end, x, y);
		} else {
			this->button = 0xdeadbeef;
		}
		break;
	case GLUT_MIDDLE_BUTTON:
		if (state == GLUT_DOWN) {
			for (int i = 0; i < 4; i++) {
				if (x - HIT_BOX <= start[i].x && x + HIT_BOX >= start[i].x && y
						- HIT_BOX <= start[i].y && y + HIT_BOX >= start[i].y) {
					mobile = &start[i];
					this->button = GLUT_MIDDLE_BUTTON;
					break;
				} else if (x - HIT_BOX <= end[i].x && x + HIT_BOX >= end[i].x
						&& y - HIT_BOX <= end[i].y && y + HIT_BOX >= end[i].y) {
					mobile = &end[i];
					this->button = GLUT_MIDDLE_BUTTON;
					break;
				}
			}
		} else {
			this->button = 0xdeadbeef;
		}
		break;
	}
}

void OpenGLImage::motionFunc(int x, int y) {
	switch (button) {
	case GLUT_LEFT_BUTTON:
		finalizeQuad(start, x, y);
		break;
	case GLUT_RIGHT_BUTTON:
		finalizeQuad(end, x, y);
		break;
	case GLUT_MIDDLE_BUTTON:
		mobile->x = x;
		mobile->y = y;
		break;
	}
}

OpenGLImage::~OpenGLImage() {
	delete[] file_data;
}
